package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"mall/api/inventory"
	"mall/api/mall"
	"mall/api/pay"
	"mall/internal/constant"
	"mall/internal/generator"
	"mall/internal/model"
	"mall/pkg/ecode"
	"mall/pkg/orm"
	"mall/pkg/util"
	"strconv"
	"time"
)

func getWebOrderStatus(order *model.Order) (status mall.MallWebOrderStatus) {
	switch mall.MallOrderStatus(order.Status) {
	case mall.MallOrderStatus_MALL_INIT:
		status = mall.MallWebOrderStatus_MALL_WEB_ORDER_WAITING_PAY
		return
	case mall.MallOrderStatus_MALL_CLOSED_AFTER_PAYED, mall.MallOrderStatus_MALL_CLOSED_BEFORE_PAYED:
		status = mall.MallWebOrderStatus_MALL_WEB_ORDER_CLOSED
		return
	}
	switch inventory.ProcessOrderStatus(order.InventoryStatus) {
	case inventory.ProcessOrderStatus_ProcessOrderWaitShip, inventory.ProcessOrderStatus_ProcessOrderWaitStock:
		status = mall.MallWebOrderStatus_MALL_WEB_ORDER_WAITING_SEND
		return
	case inventory.ProcessOrderStatus_ProcessOrderShipped, inventory.ProcessOrderStatus_ProcessOrderWaitMentioned:
		status = mall.MallWebOrderStatus_MALL_WEB_ORDER_WAITING_SIGN
		return
	case inventory.ProcessOrderStatus_ProcessOrderReceived:
		status = mall.MallWebOrderStatus_MALL_WEB_ORDER_FINISHED
		return
	}
	panic(fmt.Sprintf("商城订单后台状态分支错误: status = %d, inventory_status = %d",
		order.Status, order.InventoryStatus))
	return
}

//商家查看订单列表
func (s *Service) MerchantGetMallOrders(ctx context.Context, req *mall.MerchantGetMallOrdersRequest) (
	resp *mall.MerchantGetMallOrdersResp, err error) {
	var (
		orders    []*model.Order
		memberIds []int64
		memberMap map[uint64]string
	)
	if err = s.downstream.CheckMallPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	switch req.QueryMemberType {
	case mall.QueryMemberType_QUERY_MEMBER_TYPE_MOBILE:
		if memberIds, memberMap, err = s.downstream.SearchMemberByKeywords(
			ctx, req.QueryMemberValue, "", req.Token.SgId); err != nil {
			return
		}
	case mall.QueryMemberType_QUERY_MEMBER_TYPE_CONTACT:
		if memberIds, memberMap, err = s.downstream.SearchMemberByKeywords(
			ctx, "", req.QueryMemberValue, req.Token.SgId); err != nil {
			return
		}
	}
	resp = &mall.MerchantGetMallOrdersResp{}
	if orders, resp.Total, err = s.dao.FindWebOrdersByPage(
		ctx, req.Token.SgId, req.PStoreId, req.PageNo, req.PageSize, req.StartDate, req.EndDate,
		req.Status, memberIds, req.KeywordOrderNo,
		req.WarehouseId); err != nil {
		return
	}
	resp.Orders = make([]*mall.MallWebOrderSimpleView, len(orders))
	if len(resp.Orders) != 0 {
		var details []*model.OrderDetail
		if len(memberMap) == 0 {
			memberIdMap := make(map[int64]bool)
			for _, order := range orders {
				memberIdMap[int64(order.MemberId)] = true
			}
			for memberId := range memberIdMap {
				memberIds = append(memberIds, memberId)
			}
			if memberMap, err = s.downstream.QueryMembers(ctx, memberIds); err != nil {
				return
			}
		}
		warehouseNameMap := make(map[uint32]string)
		orderIds := make([]uint64, len(resp.Orders))
		processOrderMap := make(map[string]string)
		processOrderNumbers := make([]string, 0)
		for i, order := range orders {
			orderIds[i] = order.ID
			inventoryRemark := &model.InventoryRemark{}
			if err = json.Unmarshal(order.InventoryRemark, inventoryRemark); err != nil {
				err = ecode.WithStack(err)
				return
			}
			warehouseNameMap[order.WarehouseId] = inventoryRemark.WarehouseName
			if inventoryRemark.OrderNumber != "" {
				processOrderMap[inventoryRemark.OrderNumber] = order.OrderNumber
				processOrderNumbers = append(processOrderNumbers, inventoryRemark.OrderNumber)
			}
		}
		refundingMap := make(util.Int64BoolMap)
		storageMap := make(map[string]uint32)
		if len(processOrderNumbers) != 0 {
			var rawStatusMap map[string]uint32
			if rawStatusMap, err = s.downstream.GetOrderListStorageStatus(ctx, req.Token.SgId, processOrderNumbers); err != nil {
				return
			}
			for processOrderNumber, status := range rawStatusMap {
				storageMap[processOrderMap[processOrderNumber]] = status
			}
		}
		detailMap := make(map[uint64][]*mall.MallWebDetailSimpleView)
		if details, err = s.dao.FindOrderDetailByOrderIds(ctx, orderIds); err != nil {
			return
		}
		for _, detail := range details {
			if _, ok := detailMap[detail.OrderId]; !ok {
				detailMap[detail.OrderId] = make([]*mall.MallWebDetailSimpleView, 0)
			}
			if _, ok := refundingMap[detail.OrderId]; !ok {
				refundingMap[detail.OrderId] = detail.IsRefunding
			} else if detail.IsRefunding {
				refundingMap[detail.OrderId] = detail.IsRefunding
			}
			detailMap[detail.OrderId] = append(detailMap[detail.OrderId], &mall.MallWebDetailSimpleView{
				Id:       detail.ID,
				ProdId:   detail.ProdId,
				SkuId:    detail.SkuId,
				ProdName: detail.ProdName,
				SkuName:  detail.SkuName,
				Price:    detail.Price,
				Quantity: detail.Quantity,
				TitlePic: detail.Pic,
			})
		}
		for i, order := range orders {
			storageStatus, _ := storageMap[order.OrderNumber]
			resp.Orders[i] = &mall.MallWebOrderSimpleView{
				OrderNumber:   order.OrderNumber,
				Creator:       memberMap[order.MemberId],
				SubmitTime:    order.SubmitTime.Format(util.DefaultLayout),
				Trans:         order.Trans,
				DeliverFee:    order.DeliverFee,
				WarehouseId:   order.WarehouseId,
				WarehouseName: warehouseNameMap[order.WarehouseId],
				DeliverType:   inventory.DeliverType(order.DeliverType),
				Status:        getWebOrderStatus(order),
				RefundFlag:    refundingMap[order.ID],
				StorageStatus: storageStatus,
				Details:       detailMap[order.ID],
			}
		}
	}
	return
}

//商家查看订单详情
func (s *Service) MerchantGetMallOrderDetail(ctx context.Context, req *mall.CommonOrderRequest) (
	resp *mall.MerchantGetMallOrderDetailResp, err error) {
	if err = s.downstream.CheckMallPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	var (
		order      *model.Order
		request    *model.OrderRequest
		details    []*model.OrderDetail
		discounts  []*model.OrderDiscount
		memberMap  map[uint64]string
		storageMap map[uint64]uint32
	)
	if order, err = s.dao.GetOrderByOrderNumber(ctx, req.OrderNumber); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = constant.ErrObjectNotExist
			return
		}
		return
	}
	if request, err = s.dao.GetOrderRequestByID(ctx, order.RequestId); err != nil {
		return
	}
	if details, err = s.dao.FindOrderDetailByOrderIds(ctx, []uint64{order.ID}); err != nil {
		return
	}
	if discounts, err = s.dao.FindOrderDiscountsByOrderId(ctx, order.ID); err != nil {
		return
	}
	if memberMap, err = s.downstream.QueryMembers(ctx, []int64{int64(order.MemberId)}); err != nil {
		return
	}
	inventoryRemark := &model.InventoryRemark{}
	if err = json.Unmarshal(order.InventoryRemark, inventoryRemark); err != nil {
		err = ecode.WithStack(err)
		return
	}
	if err = json.Unmarshal(order.InventoryRemark, inventoryRemark); err != nil {
		err = ecode.WithStack(err)
		return
	}
	addressInfo := &mall.AddressInfo{}
	if err = json.Unmarshal(request.AddressInfo, addressInfo); err != nil {
		err = ecode.WithStack(err)
		return
	}
	payRemark := &model.PayRemark{}
	if err = json.Unmarshal(request.PayRemark, payRemark); err != nil {
		err = ecode.WithStack(err)
		return
	}
	if storageMap, err = s.downstream.GetOrderDetailStorageStatus(
		ctx, req.Token.SgId, inventoryRemark.OrderNumber); err != nil {
		return
	}
	resp = &mall.MerchantGetMallOrderDetailResp{
		OrderNumber:        order.OrderNumber,
		DeliverType:        inventory.DeliverType(order.DeliverType),
		Status:             getWebOrderStatus(order),
		SubmitTime:         order.SubmitTime.Format(util.DefaultLayout),
		PayOrderNumber:     request.PayOrderNumber,
		WarehouseId:        order.WarehouseId,
		WarehouseName:      inventoryRemark.WarehouseName,
		MemberId:           order.MemberId,
		Creator:            memberMap[order.MemberId],
		Receiver:           fmt.Sprintf("%s/%s", addressInfo.Contact, addressInfo.Mobile),
		Address:            addressInfo.Address,
		DeliverMemo:        &inventoryRemark.InventoryOrderUpdatePayload_DeliverMemo,
		PayTypeName:        payRemark.PayTypeName,
		Remark:             order.Remark,
		Details:            make([]*mall.MerchantOrderDetail, len(details)),
		Discounts:          make([]*mall.MerchantOrderDiscounts, len(discounts)),
		Total:              order.ProdTotal,
		DeliverFee:         order.DeliverFee,
		Discount:           order.Discount,
		Trans:              order.Trans,
		PayTime:            payRemark.PayTime,
		ProcessOrderNumber: inventoryRemark.OrderNumber,
	}
	if order.CompleteTime.Valid {
		resp.CompleteTime = order.CompleteTime.Time.Format(util.DefaultLayout)
	}
	if order.AutoConfirmTime.Valid {
		resp.AutoConfirmTime = order.AutoConfirmTime.Time.Format(util.DefaultLayout)
	}
	for i, detail := range details {
		storageStatus, _ := storageMap[detail.SkuId]
		resp.Details[i] = &mall.MerchantOrderDetail{
			Id:            detail.ID,
			ProdId:        detail.ProdId,
			SkuId:         detail.SkuId,
			ProdName:      detail.ProdName,
			SkuName:       detail.SkuName,
			Pic:           detail.Pic,
			Quantity:      detail.Quantity,
			Price:         detail.Price,
			Discount:      detail.Discount,
			Trans:         detail.Trans,
			StorageStatus: storageStatus,
		}
		if detail.IsRefunding {
			resp.RefundFlag = true
		}
	}
	for i, discount := range discounts {
		resp.Discounts[i] = &mall.MerchantOrderDiscounts{
			Name:     discount.Name,
			Discount: discount.Discount,
		}
	}
	if order.BookingDate.Valid {
		resp.BookingDate = order.BookingDate.Time.Format(util.DateLayout)
	}
	return
}

func getWebRefundStatus(refund *model.Refund) (status mall.MallWebRefundStatus) {
	switch mall.MallRefundStatus(refund.Status) {
	case mall.MallRefundStatus_MALL_REFUND_INIT:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_APPLYING
		return
	case mall.MallRefundStatus_MALL_REFUND_REJECTED:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_REJECTED
		return
	case mall.MallRefundStatus_MALL_REFUND_TO_BE_DELIVER:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_TO_BE_RETURNED
		return
	case mall.MallRefundStatus_MALL_REFUND_RETURNING:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_TO_BE_STORED
		return
	case mall.MallRefundStatus_MALL_REFUND_CLOSED:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_CLOSED
		return
	}
	switch pay.PayOrderStatus(refund.PayStatus) {
	case pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_FAILED
		return
	case pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS, pay.PayOrderStatus_REFUND_ORDER_STATUS_MARKED_SUCCESS:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_SUCCESS
		return
	case pay.PayOrderStatus_REFUND_ORDER_STATUS_REFUNDING, pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_REFUNDING
		return
	}
	switch inventory.ProcessOrderStatus(refund.InventoryStatus) {
	case inventory.ProcessOrderStatus_ProcessOrderRejectReturn:
		status = mall.MallWebRefundStatus_MALL_WEB_REFUND_WAREHOUSE_REJECTED
		return
	}
	panic(fmt.Sprintf("无法匹配后台售后单状态: status = %d, pay_status = %d, inventory_status = %d",
		refund.Status, refund.PayStatus, refund.InventoryStatus))
}

//商家视角退款申请列表
func (s *Service) MerchantQueryRefunds(ctx context.Context, req *mall.MerchantQueryRefundsRequest) (
	resp *mall.MerchantQueryRefundsResp, err error) {
	if err = s.downstream.CheckMallPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	var (
		applies    []*model.Refund
		reqOrderId uint64
	)
	if req.OrderNo != "" {
		var order *model.Order
		if order, err = s.dao.GetOrderByOrderNumber(ctx, req.OrderNo); err != nil {
			return
		}
		reqOrderId = order.ID
	}
	resp = &mall.MerchantQueryRefundsResp{}
	if applies, resp.Total, err = s.dao.FindWebRefundsByPage(ctx, req.Token.SgId, req.PStoreId, req.PageNo, req.PageSize,
		req.StartDate, req.EndDate, req.Category, req.Type, reqOrderId, req.ApplyNo); err != nil {
		return
	}
	resp.Applies = make([]*mall.MallWebRefundSimpleView, len(applies))
	if len(applies) != 0 {
		var (
			orders  []*model.Order
			details []*model.RefundDetail
		)
		applyIds := make([]uint64, len(applies))
		orderIdMap := make(util.Int64BoolMap)
		orderNumberMap := make(map[uint64]string)
		for i, apply := range applies {
			applyIds[i] = apply.ID
			orderIdMap[apply.OrderId] = true
		}
		if orders, err = s.dao.FindOrdersByOrderIds(ctx, orderIdMap.GetKeys()); err != nil {
			return
		}
		for _, order := range orders {
			orderNumberMap[order.ID] = order.OrderNumber
		}
		if details, err = s.dao.FindRefundDetailsByApplyIds(ctx, applyIds); err != nil {
			return
		}
		detailMap := make(map[uint64][]*mall.MallWebRefundDetailSimpleView)
		for _, detail := range details {
			if _, ok := detailMap[detail.ApplyId]; !ok {
				detailMap[detail.ApplyId] = make([]*mall.MallWebRefundDetailSimpleView, 0)
			}
			detailMap[detail.ApplyId] = append(detailMap[detail.ApplyId], &mall.MallWebRefundDetailSimpleView{
				Id:       detail.ID,
				ProdId:   detail.ProdId,
				SkuId:    detail.SkuId,
				ProdName: detail.ProdName,
				SkuName:  detail.SkuName,
				Price:    detail.Price,
				Quantity: detail.Quantity,
				TitlePic: detail.Pic,
			})
		}
		for i, apply := range applies {
			resp.Applies[i] = &mall.MallWebRefundSimpleView{
				ApplyNumber: apply.ApplyNumber,
				OrderNumber: orderNumberMap[apply.OrderId],
				ApplyTime:   apply.ApplyTime.Format(util.DefaultLayout),
				Type:        mall.MallRefundType(apply.ApplyType),
				Amount:      apply.Trans,
				Status:      getWebRefundStatus(apply),
				Details:     detailMap[apply.ID],
			}
		}
	}
	return
}

//商家视角退款申请详情
func (s *Service) MerchantGetRefundApply(ctx context.Context, req *mall.MerchantGetRefundRequest) (
	resp *mall.MerchantGetRefundResp, err error) {
	if err = s.downstream.CheckMallPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	var (
		apply     *model.Refund
		order     *model.Order
		memberMap map[uint64]string
		details   []*model.RefundDetail
	)
	if apply, err = s.dao.GetApplyByApplyNumber(ctx, req.ApplyNumber); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = constant.ErrObjectNotExist
			return
		}
		return
	}
	if order, err = s.dao.GetOrderByOrderID(ctx, apply.OrderId); err != nil {
		return
	}
	if details, err = s.dao.FindRefundDetailsByApplyIds(ctx, []uint64{apply.ID}); err != nil {
		return
	}
	if memberMap, err = s.downstream.QueryMembers(ctx, []int64{int64(apply.MemberId)}); err != nil {
		return
	}
	refundApplyRemark := &model.RefundApplyRemark{}
	refundPaymentRemark := &model.RefundPaymentRemark{}
	reviewRemark := &model.ReviewRemark{}
	warehouseRemark := &model.WarehouseRemark{}
	if err = json.Unmarshal(apply.Remark, refundApplyRemark); err != nil {
		err = ecode.WithStack(err)
		return
	}
	if err = json.Unmarshal(apply.PayRemark, refundPaymentRemark); err != nil {
		err = ecode.WithStack(err)
		return
	}
	if err = json.Unmarshal(apply.ReviewRemark, reviewRemark); err != nil {
		err = ecode.WithStack(err)
		return
	}
	if err = json.Unmarshal(apply.WarehouseInfo, warehouseRemark); err != nil {
		err = ecode.WithStack(err)
		return
	}
	resp = &mall.MerchantGetRefundResp{
		ApplyNumber:      apply.ApplyNumber,
		OrderNumber:      order.OrderNumber,
		ApplyTime:        apply.ApplyTime.Format(util.DefaultLayout),
		Type:             mall.MallRefundType(apply.ApplyType),
		Amount:           apply.Trans,
		Status:           getWebRefundStatus(apply),
		Contact:          memberMap[apply.MemberId],
		Details:          make([]*mall.OrderRefundDetail, len(details)),
		Platform:         warehouseRemark.Platform,
		PlatformOrderNum: warehouseRemark.PlatformOrderNum,
		DeliverTime:      warehouseRemark.DeliverTime,
		ReceiveTime:      warehouseRemark.CompleteTime,
		Memo:             refundApplyRemark.Memo,
		ReviewOperator:   reviewRemark.Operator,
		ReviewReason:     reviewRemark.Reason,
		CancelReason:     refundApplyRemark.Reason,
		FailedReason:     refundPaymentRemark.FailedReason,
		RejectReason:     warehouseRemark.Reason,
		RejectOperator:   warehouseRemark.Operator,
		PayTypeName:      refundPaymentRemark.PayTypeName,
		WarehouseName:    warehouseRemark.Name,
	}
	switch mall.MallRefundStatus(apply.Status) {
	case mall.MallRefundStatus_MALL_REFUND_REJECTED:
		resp.CompleteTime = resp.ReviewTime
	case mall.MallRefundStatus_MALL_REFUND_CLOSED:
		resp.CompleteTime = apply.UpdatedAt.Format(util.DefaultLayout)
	}
	switch inventory.ProcessOrderStatus(apply.InventoryStatus) {
	case inventory.ProcessOrderStatus_ProcessOrderRejectReturn:
		resp.CompleteTime = warehouseRemark.CompleteTime
	}
	switch pay.PayOrderStatus(apply.PayStatus) {
	case pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS, pay.PayOrderStatus_REFUND_ORDER_STATUS_MARKED_SUCCESS:
		resp.CompleteTime = refundPaymentRemark.PayTime
	}
	if apply.ReviewTime.Valid {
		resp.ReviewTime = apply.ReviewTime.Time.Format(util.DefaultLayout)
	}
	for i, detail := range details {
		resp.Details[i] = &mall.OrderRefundDetail{
			Id:       detail.ID,
			ProdId:   detail.ProdId,
			SkuId:    detail.SkuId,
			ProdName: detail.ProdName,
			SkuName:  detail.SkuName,
			Price:    detail.Price,
			Quantity: detail.Quantity,
			TitlePic: detail.Pic,
			Reason:   detail.Reason,
			Trans:    detail.Trans,
		}
	}
	return
}

func (s *Service) processRefundInTransaction(ctx context.Context, apply *model.Refund,
	refundOrderView *pay.RefundOrderView) (err error) {
	var refundRemarkJson json.RawMessage
	apply.PayStatus = uint8(refundOrderView.Status)
	if refundRemarkJson, err = json.Marshal(&model.RefundPaymentRemark{
		PayRemark: model.PayRemark{
			PayType:          refundOrderView.PayType,
			PayTypeName:      refundOrderView.PayTypeName,
			OriginAmount:     refundOrderView.OriginAmount,
			MerchantDiscount: refundOrderView.MerchantDiscount,
			PlatformDiscount: refundOrderView.PlatformDiscount,
			UserPayed:        refundOrderView.UserPayed,
			Receive:          refundOrderView.Receive,
			PayTime:          refundOrderView.PayTime,
		},
		RefundOrderNumber: refundOrderView.OrderNumber,
		FailedReason:      refundOrderView.FailedReason,
	}); err != nil {
		err = ecode.WithStack(err)
		return
	}
	apply.PayRemark = orm.JSON(refundRemarkJson)
	if err = s.dao.Save(ctx, apply); err != nil {
		return
	}
	if mall.MallRefundType(apply.ApplyType) == mall.MallRefundType_MALL_REFUND_CANCEL_BEFORE_SENDING {
		switch refundOrderView.Status {
		case pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS, pay.PayOrderStatus_REFUND_ORDER_STATUS_MARKED_SUCCESS:
			// 标记订单完结时间
			order := &model.Order{}
			if err = s.dao.LockOrderByID(ctx, order, apply.OrderId); err != nil {
				return
			}
			order.CompleteTime = sql.NullTime{
				Valid: true, Time: util.ParseTimeWithZero(refundOrderView.PayTime)}
			if err = s.dao.Save(ctx, order); err != nil {
				return
			}
		}
	}
	return
}

//商家审核退款申请
func (s *Service) MerchantOperateRefundApply(ctx context.Context, req *mall.MerchantOperateRefundApplyRequest) (
	resp *mall.DefaultResp, err error) {
	if err = s.downstream.CheckMallPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	var schedule *model.Schedule
	apply := &model.Refund{}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		var (
			details []*model.RefundDetail
			order   *model.Order
		)
		if err = s.dao.LockApply(ctx, apply, req.ApplyNumber); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
		if apply.PStoreId != req.PStoreId || apply.SgId != req.Token.SgId {
			err = constant.ErrObjectNotExist
			return
		}
		now := time.Now()
		if order, err = s.dao.GetOrderByOrderID(ctx, apply.OrderId); err != nil {
			return
		}
		applyRemark := &model.RefundApplyRemark{}
		if err = json.Unmarshal(apply.Remark, applyRemark); err != nil {
			err = ecode.WithStack(err)
			return
		}
		reviewRemark := &model.ReviewRemark{}
		if err = json.Unmarshal(apply.ReviewRemark, reviewRemark); err != nil {
			err = ecode.WithStack(err)
			return
		}
		reviewRemark.Operator = req.Token.EmpName
		reviewRemark.EmpID = req.Token.EmpId
		if details, err = s.dao.FindRefundDetailsByApplyIds(ctx, []uint64{apply.ID}); err != nil {
			return
		}
		detailIds := make([]uint64, len(details))
		for i, detail := range details {
			detailIds[i] = detail.DetailId
		}
		if err = s.dao.UpdateDetailIsRefundingByDetailIds(ctx, detailIds, false); err != nil {
			return
		}
		if err = s.checkRefunding(ctx, apply, order, now); err != nil {
			return
		}
		if req.Approve {
			var (
				cancelMsg json.RawMessage
				request   *model.OrderRequest
			)
			if request, err = s.dao.GetOrderRequestByID(ctx, order.RequestId); err != nil {
				return
			}
			switch mall.MallRefundType(apply.ApplyType) {
			case mall.MallRefundType_MALL_REFUND_CANCEL_BEFORE_SENDING:
				apply.Status = uint8(mall.MallRefundStatus_MALL_REFUND_FINISHED)
				order.Status = uint8(mall.MallOrderStatus_MALL_CLOSED_AFTER_PAYED)
				order.InventoryStatus = uint8(inventory.ProcessOrderStatus_ProcessOrderCancelled)
				order.Refunded = order.Trans
				order.CompleteTime = sql.NullTime{Valid: true, Time: now}
				if err = s.dao.Save(ctx, order); err != nil {
					return
				}
				if cancelMsg, err = json.Marshal(&mall.MallOrderCancelMessage{
					BizNo:       request.BizNo,
					OrderNumber: order.OrderNumber,
					Timestamp:   now.Unix(),
				}); err != nil {
					return
				}
				if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicOrderCancel, cancelMsg); err != nil {
					return
				}
				// 创建退款定时任务
				schedule = &model.Schedule{
					ScheduleType: uint8(mall.ScheduleType_SCHEDULE_TYPE_REFUND_CREATE),
					OrderId:      apply.ID,
					ExecuteTime:  now.Add(time.Second * 30),
				}
				if err = s.dao.Save(ctx, schedule); err != nil {
					return
				}
				// 取消发货单
				if err = s.downstream.CancelProcessOrder(ctx, order.OrderNumber, req.Reason); err != nil {
					return
				}
			case mall.MallRefundType_MALL_REFUND_ONLY:
				apply.Status = uint8(mall.MallRefundStatus_MALL_REFUND_FINISHED)
				// 创建退款定时任务
				schedule = &model.Schedule{
					ScheduleType: uint8(mall.ScheduleType_SCHEDULE_TYPE_REFUND_CREATE),
					OrderId:      apply.ID,
					ExecuteTime:  now.Add(time.Second * 30),
				}
				if err = s.dao.Save(ctx, schedule); err != nil {
					return
				}
			case mall.MallRefundType_MALL_REFUND_WITH_DETAIL:
				apply.Status = uint8(mall.MallRefundStatus_MALL_REFUND_TO_BE_DELIVER)
			default:
				err = fmt.Errorf("退款类型分支不合法: %d", apply.ApplyType)
				return
			}
		} else {
			apply.Status = uint8(mall.MallRefundStatus_MALL_REFUND_REJECTED)
			reviewRemark.Reason = req.Reason
		}
		if apply.ReviewRemark, err = json.Marshal(reviewRemark); err != nil {
			err = ecode.WithStack(err)
			return
		}
		if err = s.dao.Save(ctx, apply); err != nil {
			return
		}
		return
	}); err != nil {
		return
	}
	if schedule != nil {
		if err = s.createRefund(ctx, apply.ID); err != nil {
			return
		}
	}
	resp = &mall.DefaultResp{Status: true}
	return
}

//商家取消未发货订单
func (s *Service) MerchantCancelOrder(ctx context.Context, req *mall.MerchantCancelOrderRequest) (
	resp *mall.DefaultResp, err error) {
	if err = s.downstream.CheckMallPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	var (
		apply    *model.Refund
		schedule *model.Schedule
	)
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		var (
			request                                                *model.OrderRequest
			applies                                                []*model.Refund
			cancelMsg                                              json.RawMessage
			applyNumber                                            string
			details                                                []*model.OrderDetail
			refundRemark, warehouseRemark, payRemark, reviewRemark json.RawMessage
			warehouseInfo                                          *inventory.GetWarehouseAddressInfoReply
		)
		now := time.Now()
		order := &model.Order{}
		if err = s.dao.LockOrder(ctx, order, req.OrderNumber); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
		if order.PStoreId != req.PStoreId || order.SgId != req.Token.SgId {
			err = constant.ErrObjectNotExist
			return
		}
		switch inventory.ProcessOrderStatus(order.InventoryStatus) {
		case inventory.ProcessOrderStatus_ProcessOrderWaitStock, inventory.ProcessOrderStatus_ProcessOrderWaitShip:
		default:
			err = ecode.AppendMessage(constant.ErrForbiddenAction,
				"只允许未备货/发货的订单进行整单取消")
			return
		}
		if applies, err = s.dao.FindOrderRefundsByOrderIds(ctx, []uint64{order.ID}); err != nil {
			return
		}
		for _, apply := range applies {
			switch mall.MallRefundStatus(apply.Status) {
			case mall.MallRefundStatus_MALL_REFUND_CLOSED, mall.MallRefundStatus_MALL_REFUND_REJECTED:
			default:
				err = ecode.AppendMessage(constant.ErrForbiddenAction,
					"只允许没有进行中或已成功退款的订单进行整单取消")
				return
			}
		}
		if request, err = s.dao.GetOrderRequestByID(ctx, order.RequestId); err != nil {
			return
		}
		if details, err = s.dao.FindOrderDetailByOrderIds(ctx, []uint64{order.ID}); err != nil {
			return
		}
		order.Status = uint8(mall.MallOrderStatus_MALL_CLOSED_AFTER_PAYED)
		order.InventoryStatus = uint8(inventory.ProcessOrderStatus_ProcessOrderCancelled)
		order.Refunded = order.Trans
		order.CompleteTime = sql.NullTime{Valid: true, Time: now}
		if err = s.dao.Save(ctx, order); err != nil {
			return
		}
		// 创建自动审核通过的退款申请来记录取消信息
		if applyNumber, err = generator.GenerateOrderNumber(
			now, int(mall.OrderType_ORDER_TYPE_REFUND), strconv.Itoa(int(order.MemberId))); err != nil {
			return
		}
		if refundRemark, err = json.Marshal(&model.RefundApplyRemark{
			Memo:   "商家直接取消",
			Reason: req.Reason,
		}); err != nil {
			err = ecode.WithStack(err)
			return
		}
		if warehouseInfo, err = s.downstream.GetWarehouseInfo(
			ctx, order.SgId, order.PStoreId, order.WarehouseId); err != nil {
			return
		}
		if warehouseRemark, err = json.Marshal(&model.WarehouseRemark{
			AddressInfo: *warehouseInfo.AddressInfo,
			Name:        warehouseInfo.Name,
		}); err != nil {
			return
		}
		if reviewRemark, err = json.Marshal(&model.ReviewRemark{
			EmpID:    req.Token.EmpId,
			Operator: req.Token.EmpName,
		}); err != nil {
			return
		}
		requestPayRemark := &model.PayRemark{}
		if err = json.Unmarshal(request.PayRemark, requestPayRemark); err != nil {
			return
		}
		if payRemark, err = json.Marshal(&model.PayRemark{
			PayType:     requestPayRemark.PayType,
			PayTypeName: requestPayRemark.PayTypeName,
		}); err != nil {
			err = ecode.WithStack(err)
			return
		}
		apply = &model.Refund{
			ApplyNumber:   applyNumber,
			OrderId:       order.ID,
			ApplyType:     uint8(mall.MallRefundType_MALL_REFUND_CANCEL_BEFORE_SENDING),
			Status:        uint8(mall.MallRefundStatus_MALL_REFUND_FINISHED),
			SgId:          order.SgId,
			PStoreId:      order.PStoreId,
			MemberId:      order.MemberId,
			ApplyTime:     now,
			Remark:        orm.JSON(refundRemark),
			WarehouseId:   order.WarehouseId,
			WarehouseInfo: orm.JSON(warehouseRemark),
			Trans:         order.Trans,
			ReviewTime:    sql.NullTime{Valid: true, Time: now},
			ReviewRemark:  orm.JSON(reviewRemark),
			PayStatus:     uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT),
			PayRemark:     orm.JSON(payRemark),
			DeliverType:   uint8(inventory.DeliverType_SKIP),
		}
		if err = s.dao.Create(ctx, apply); err != nil {
			return
		}
		for _, detail := range details {
			if err = s.dao.Create(ctx, &model.RefundDetail{
				ApplyId:  apply.ID,
				MemberId: order.MemberId,
				OrderId:  order.ID,
				DetailId: detail.ID,
				ProdId:   detail.ProdId,
				SkuId:    detail.SkuId,
				ProdName: detail.ProdName,
				SkuName:  detail.SkuName,
				Pic:      detail.Pic,
				Unit:     detail.Unit,
				Price:    detail.Price,
				Quantity: detail.Quantity,
				Trans:    detail.Trans,
			}); err != nil {
				return
			}
		}
		if cancelMsg, err = json.Marshal(&mall.MallOrderCancelMessage{
			BizNo:       request.BizNo,
			OrderNumber: order.OrderNumber,
			Timestamp:   now.Unix(),
		}); err != nil {
			return
		}
		if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicOrderCancel, cancelMsg); err != nil {
			return
		}
		// 创建退款定时任务
		schedule = &model.Schedule{
			ScheduleType: uint8(mall.ScheduleType_SCHEDULE_TYPE_REFUND_CREATE),
			OrderId:      apply.ID,
			ExecuteTime:  now.Add(time.Second * 30),
		}
		if err = s.dao.Save(ctx, schedule); err != nil {
			return
		}
		// 取消发货单
		if err = s.downstream.CancelProcessOrder(ctx, order.OrderNumber, req.Reason); err != nil {
			return
		}
		return
	}); err != nil {
		return
	}
	if schedule != nil {
		if err = s.createRefund(ctx, apply.ID); err != nil {
			return
		}
	}
	resp = &mall.DefaultResp{Status: true}
	return
}

//商家退款失败重试
func (s *Service) MerchantRetryRefund(ctx context.Context, req *mall.MerchantRetryRefundRequest) (
	resp *mall.DefaultResp, err error) {
	if err = s.downstream.CheckMallPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		var refundOrderView *pay.RefundOrderView
		apply := &model.Refund{}
		if err = s.dao.LockApply(ctx, apply, req.ApplyNumber); err != nil {
			return
		}
		if apply.PStoreId != req.PStoreId || apply.SgId != req.Token.SgId {
			err = constant.ErrObjectNotExist
			return
		}
		refundPaymentRemark := &model.RefundPaymentRemark{}
		if err = json.Unmarshal(apply.PayRemark, refundPaymentRemark); err != nil {
			return
		}
		if refundOrderView, err = s.downstream.RefundRetry(ctx, refundPaymentRemark.RefundOrderNumber,
			req.Token.EmpId, req.Marked); err != nil {
			return
		}
		if err = s.processRefundInTransaction(ctx, apply, refundOrderView); err != nil {
			return
		}
		return
	}); err != nil {
		return
	}
	resp = &mall.DefaultResp{Status: true}
	return
}
